Introduction

To re-cap - we have obtained our raw counts matrices from GEO (or cellranger). We have performed QC and subset the data according to the QC plots we generated. We have normalised the data via sctransform - regressing out unwanted sources of variation, and integrated across our datasets. Lastly, we have clustered the cells into separate groups based on the similarity of their expression profiles.

In the next part of the course we are going to explore the genes that show the most differences in their expression between clusters. The top genes showing the biggest change in their expression compared to the other clusters are often called marker genes. We can then use these marker genes to assign an identity class to our clusters of interest.

Learning Objectives

  • Find differentially expressed genes using FindMarkers() and FindAllMarkers().
  • Use the bioconductor human annotation package org.Hs.eg.db to add some more useful annotation to our marker genes.
  • Assign cluster identity using cell type canonical marker genes
  • Carry out a simple pathway analysis

Firstly, we will load up the packages we have been using so far for this part of the course.

library(Seurat, quietly = T)
library(tidyverse, quietly = T)

And then we can load up the R objects we have used in the previous sessions.

seurat <- readRDS("Robjects/seurat_integrated.RDS")

We can simply run the name of the Seurat object (in our case seurat) to give us a summary of what the object contains and what calculations have been performed on it.

seurat
An object of class Seurat 
31850 features across 3322 samples within 3 assays 
Active assay: integrated (3000 features, 3000 variable features)
 2 other assays present: RNA, SCT
 3 dimensional reductions calculated: pca, tsne, umap

We can recap our clusters by taking a look at the DimPlot

DimPlot(seurat, reduction = "umap")

Find differentially expressed genes

Our next objective is to start assigning some biological meaning to our data. We have some nice clusters but no clue yet what cell types our clusters contain.

In Seurat we can find the top differentially expressed genes -or markers - within each cluster. The function FindMarkers() handles most of the differential expression testing within the Seurat package and finds marker genes within a particular cluster. As a default, Seurat performs differential expression based on the non-parameteric Wilcoxon rank sum test but other methods are available within the function.

To start with we can find all the differentially expressed genes in cluster 3 compared to every other cluster. The min.pct argument requires a gene to be detected at a minimum percentage of all the cells.

Since we have performed integration in a previous step, we set the default assay back to ‘RNA’ to ensure we are using the raw counts, rather than counts transformed by integration, for our differential analysis.

# We make sure the default assay is set back to "RNA" to use the original counts for DE analysis.
DefaultAssay(seurat) <- "RNA"
cluster3.markers <- FindMarkers(seurat, 
                                ident.1 = 3, 
                                min.pct  = 0.25)
cluster3.markers %>% head

A quick look at the top 6 lines in our differentially expressed marker gene table shows us the format of the data. We have the results of the Wilcoxon rank sum test as a p value and adjusted p value for multiple testing. We also have our average log2 fold change in expression between our clusters. The pct.1 and pct.2 columns show us the percentage of cells within the cluster the gene is expressed in (we set a minimum threshold of 20% in the function). As default, the output table is sorted by adjusted p value.

A littlebit of table manipulation in dplyr makes the markers easier to view…

cluster3.markers  %>% 
  select(avg_log2FC, p_val, p_val_adj) %>%
  slice_max(n = 5, order_by = avg_log2FC) 

To check our DE findings, we can visualize expression of a particular gene of interest across all clusters using the VlnPlot() function. This shows the expression levels of a gene (or genes) of interest within the cells across all clusters.

VlnPlot(seurat, features = "LYZ")

We can also check which cells express our gene of interest on the UMAP image using FeaturePlot

FeaturePlot(seurat, features = "LYZ")

Perhaps we are most interested in the differentially expressed genes between clusters 1 and 3

cluster1.markers <- FindMarkers(seurat, 
                                ident.1 = 1, 
                                ident.2 = 3, 
                                min.pct = 0.25,verbose = FALSE)
cluster1.markers %>% head()

… or the DE genes between clusters 1 in comparison to 3 and 4?

cluster1.markers <- FindMarkers(seurat, ident.1 = 1, 
                                ident.2 = c(3,4), # we can group clusters together!
                                min.pct = 0.25,
                                verbose = FALSE)
cluster1.markers %>% head()

Usually, we are wanting to find the most differentially expressed genes in each cluster across the whole dataset. For this we use FindAllMarkers. This time we will only report the positively expressed markers with a minimum log fold change of 0.25.

markers <- FindAllMarkers(seurat, 
                          only.pos = TRUE, 
                          min.pct = 0.25, 
                          logfc.threshold = 0.25, 
                          verbose = F) # keeps it quiet!
markers

Again, a bit of manipulation in dplyr enables us to get the top 5 DE genes in each cluster.

markers %>%
  select(gene, cluster, avg_log2FC, p_val, p_val_adj) %>%
    group_by(cluster) %>% # important to look in each cluster!
    slice_max(n = 5, order_by = avg_log2FC)

Exercise

  • Use FindMarkers to find the differentially expressed genes in clusters 1 & 2 vs 8
  • Can you use dplyr to select columns of interest and order them by log fold change?
  • Use one of the visualisation tools to look at the expression of one of the top marker genes
  • Bonus! - We have not discussed the visualisation tool RidgePlot. See if you can use it to make another plot of one of the top marker genes. More info here - https://satijalab.org/seurat/articles/visualization_vignette.html
#Put exercise work in here :-)

Annotating marker genes

We may want to share our findings with collaborators or perform further bioinformatic analysis on our DE genes in clusters of interest. In this dataset, the gene level information is given as a gene name. Whilst this is useful we may want to embellish these results with some gene description information or some other gene IDs (eg Ensembl, Entrez).

The org.Hs.eg.db database is a genome wide annotation package for human that has many different gene mappings based on Entrez Gene identifiers. These are up to data databases of gene annotation that can be installed like any other bioconductor package

# Dont run this if you have run already!
#install.packages("BiocManager")
#BiocManager::install("org.Hs.eg.db")

library(org.Hs.eg.db)

In our case we are going to use the gene symbols (rownames in our Seurat object) to extract the gene description and entrez ID from the database.

We are going to filter the database by a key or set of keys in order to extract the information we want. Valid names for the key can be retrieved with the keytypes function.

keytypes(org.Hs.eg.db)
 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"      "ENSEMBLPROT"  "ENSEMBLTRANS" "ENTREZID"     "ENZYME"       "EVIDENCE"     "EVIDENCEALL" 
[10] "GENENAME"     "GENETYPE"     "GO"           "GOALL"        "IPI"          "MAP"          "OMIM"         "ONTOLOGY"     "ONTOLOGYALL" 
[19] "PATH"         "PFAM"         "PMID"         "PROSITE"      "REFSEQ"       "SYMBOL"       "UCSCKG"       "UNIPROT"     

We are going to use the keytype ‘SYMBOL’ which designates the gene names that we have in our rownames of the Seurat object.

Unfortunately, the authors of dplyr and AnnotationDbi have both decided to use the name select in their packages. To avoid confusion and problems from packages attempting to use the wrong tool, the following code is sometimes used:-

AnnotationDbi::select which tells us to use the ‘select’ function from the AnnotationDBI package.

anno <- AnnotationDbi::select(org.Hs.eg.db,
                              keys=rownames(seurat),
                              columns=c("SYMBOL","GENENAME", "ENTREZID"),  # info we want to extract
                              keytype = "SYMBOL") # information we have

# Check it worked!
head(anno)  

Now we can combine our annotation table to our marker genes using dplyr and left_join

anno_markers <- markers %>% 
  dplyr::select(gene, cluster, avg_log2FC, p_val, p_val_adj) %>%
  left_join(anno, by = c("gene" = "SYMBOL"))

anno_markers %>% head()
NA

At this point we might want to save our result into a nicely formatted file where we can look up our clusters, top DE genes and their gene descriptions.

anno_markers %>% write_csv("Annotated.results.csv")

Assigning cluster identity

If you recall, our dataset is from the following paper:-

Caron M, St-Onge P, Sontag T, Wang YC et al. Single-cell analysis of childhood leukemia reveals a link between developmental states and ribosomal protein expression as a source of intra-individual heterogeneity. Sci Rep 2020 May15;10(1):8079

This paper uses the following canonical marker genes to assign cell cluster identity. This starts to give some biological meaning to our results.

These genes are known to be expressed in a particular cell type.

Gene Cell type
CD79A B Cells
CD34 CD34+,
MS4A1 CD20+ B cells,
MZB1 BCMA,
CST3 Monocytes,
SPN Im mature hematopoietic,
HBA1 erythrocytes,
CD3D T cells,
NKG7 NK cells

We can assign these marker genes as a variable

cell_markers <- c("CD79A","CD34", "MS4A1", "MZB1","CST3" ,"SPN", "KIT", "HBA1", "CD3D", "NKG7")

Dot plots allow us to examine particular features or genes of interest across all clusters. Creating a dotplot of the cell type markers lets us assign identity based on their expression.

DotPlot(seurat, features = cell_markers) + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

We can then manually create new identities for each cluster as follows.

new.cluster.ids <- c("T Cells", #0
                     "T Cells", #1
                     "Erythrocytes", #2  
                     "Monocytes",  #3
                     "B Cells", #4
                     "CD34+", #5
                     "B Cells", #6
                     "NK Cells", #7
                     "Immature hema#8topoietic", 
                     "CD79A B cells",#9 
                     "Monocytes", #10
                     "CD20+ B cells", #11
                     "BCMA", #12
                     "Erythrocytes", #13 
                     "BCMA") #14
names(new.cluster.ids) <- levels(seurat)
new.cluster.ids
                         0                          1                          2                          3                          4 
                 "T Cells"                  "T Cells"             "Erythrocytes"                "Monocytes"                  "B Cells" 
                         5                          6                          7                          8                          9 
                   "CD34+"                  "B Cells"                 "NK Cells" "Immature hema#8topoietic"            "CD79A B cells" 
                        10                         11                         12                         13                         14 
               "Monocytes"            "CD20+ B cells"                     "BCMA"             "Erythrocytes"                     "BCMA" 
seurat_named <- RenameIdents(seurat, new.cluster.ids)
DimPlot(seurat_named, reduction = "umap", label = TRUE, pt.size = 0.5) + NoLegend()

We can check our result to the UMAP plot produced from the full dataset within the paper - not too bad! Considering we have used a subset and not followed their exact method.

GO term and pathway enrichment

Sometimes we want to carry out a Gene Enrichment Analysis on a list of genes of interest. This enables us to find functional terms that are statistically over represented within a group of genes compared to a background list. clusterProfiler is an R package that provides statistical tests for expression analysis of terms such as GO (Gene Ontology), within gene lists that have shown statistically significant differences.

When using this tool we need to have two inputs: a list of genes that show significant differentiation in expression and a list of background genes. Hypergeometric tests are used for the statistical enrichment analysis.

We can check for GO terms measuring BP (biological process), CC (cell component) and MF (molecular function).

First of all we need to create our test genes and background list

library(clusterProfiler)
background <- anno %>% pull("ENTREZID")

go_markers <- anno_markers %>% filter(cluster ==c(0,1)) %>% pull(ENTREZID) # Lets look at clusters (1 + 2 -  T cells)

  eGO <- enrichGO(
    gene          = go_markers,
    universe = background, 
    OrgDb         = org.Hs.eg.db,
    ont           = "BP",
    pAdjustMethod = "BH",
    pvalueCutoff  = 0.05,
    readable      = TRUE
  )
  
head(eGO)

clusterProfiler also has functionality to plot pathway diagrams for us. It uses our new object created by enrichGO as input and highlights the pathways enriched in our dataset.

plotGOgraph(eGO)
$dag
A graphNEL graph with directed edges
Number of Nodes = 65 
Number of Edges = 109 

$complete.dag
[1] "A graph with 65 nodes."

LS0tCnRpdGxlOiAiQW5hbHlzaXMgb2YgU2luZ2xlLUNlbGwgUk5BLXNlcSAtIFNlc3Npb24gNCIKYXV0aG9yOiAiRW1pbHkgViBDaGFtYmVycyIKZGF0ZTogJ2ByIGZvcm1hdChTeXMudGltZSgpLCAiTGFzdCBtb2RpZmllZDogJWQgJWIgJVkiKWAnCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNzczogc3R5bGVzaGVldHMvc3R5bGVzLmNzcwotLS0KPGltZyBzcmM9ImltYWdlcy9sb2dvLXNtLnBuZyIgc3R5bGU9InBvc2l0aW9uOmFic29sdXRlO3RvcDo0MHB4O3JpZ2h0OjEwcHg7IiB3aWR0aD0iMjAwIiAvPgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICB0aWR5ID0gVFJVRSwKICB0aWR5Lm9wdHMgPSBsaXN0KHdpZHRoLmN1dG9mZiA9IDk1KSwKICBtZXNzYWdlID0gRkFMU0UsCiAgd2FybmluZyA9IEZBTFNFLAogIGZpZy53aWR0aCA9IDEwLAogIHRpbWVfaXQgPSBUUlVFCikKCmBgYAoKIyBJbnRyb2R1Y3Rpb24KClRvIHJlLWNhcCAtICB3ZSBoYXZlIG9idGFpbmVkIG91ciByYXcgY291bnRzIG1hdHJpY2VzIGZyb20gR0VPIChvciBjZWxscmFuZ2VyKS4gV2UgaGF2ZSBwZXJmb3JtZWQgUUMgYW5kIHN1YnNldCB0aGUgZGF0YSBhY2NvcmRpbmcgdG8gdGhlIFFDIHBsb3RzIHdlIGdlbmVyYXRlZC4gV2UgaGF2ZSBub3JtYWxpc2VkIHRoZSBkYXRhIHZpYSBzY3RyYW5zZm9ybSAtIHJlZ3Jlc3Npbmcgb3V0IHVud2FudGVkIHNvdXJjZXMgb2YgdmFyaWF0aW9uLCBhbmQgaW50ZWdyYXRlZCBhY3Jvc3Mgb3VyIGRhdGFzZXRzLiBMYXN0bHksIHdlIGhhdmUgY2x1c3RlcmVkIHRoZSBjZWxscyBpbnRvIHNlcGFyYXRlIGdyb3VwcyBiYXNlZCBvbiB0aGUgc2ltaWxhcml0eSBvZiB0aGVpciBleHByZXNzaW9uIHByb2ZpbGVzLgoKSW4gdGhlIG5leHQgcGFydCBvZiB0aGUgY291cnNlIHdlIGFyZSBnb2luZyB0byBleHBsb3JlIHRoZSBnZW5lcyB0aGF0IHNob3cgdGhlIG1vc3QgZGlmZmVyZW5jZXMgaW4gdGhlaXIgZXhwcmVzc2lvbiBiZXR3ZWVuIGNsdXN0ZXJzLiBUaGUgdG9wIGdlbmVzIHNob3dpbmcgdGhlIGJpZ2dlc3QgY2hhbmdlIGluIHRoZWlyIGV4cHJlc3Npb24gY29tcGFyZWQgdG8gdGhlIG90aGVyIGNsdXN0ZXJzIGFyZSBvZnRlbiBjYWxsZWQgKm1hcmtlciBnZW5lcyouIFdlIGNhbiB0aGVuIHVzZSB0aGVzZSBtYXJrZXIgZ2VuZXMgdG8gYXNzaWduIGFuICppZGVudGl0eSBjbGFzcyogdG8gb3VyIGNsdXN0ZXJzIG9mIGludGVyZXN0LgoKIVtdKGltYWdlcy9mbG93Y2hhcnQzLnBuZykKCiMjIExlYXJuaW5nIE9iamVjdGl2ZXMKKiBGaW5kIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyB1c2luZyBgRmluZE1hcmtlcnMoKWAgYW5kIGBGaW5kQWxsTWFya2VycygpYC4KKiBVc2UgdGhlIGJpb2NvbmR1Y3RvciBodW1hbiBhbm5vdGF0aW9uIHBhY2thZ2UgYG9yZy5Icy5lZy5kYmAgdG8gYWRkIHNvbWUgbW9yZSB1c2VmdWwgYW5ub3RhdGlvbiB0byBvdXIgbWFya2VyIGdlbmVzLgoqIEFzc2lnbiBjbHVzdGVyIGlkZW50aXR5IHVzaW5nIGNlbGwgdHlwZSBjYW5vbmljYWwgbWFya2VyIGdlbmVzCiogQ2Fycnkgb3V0IGEgc2ltcGxlIHBhdGh3YXkgYW5hbHlzaXMKCkZpcnN0bHksIHdlIHdpbGwgbG9hZCB1cCB0aGUgcGFja2FnZXMgd2UgaGF2ZSBiZWVuIHVzaW5nIHNvIGZhciBmb3IgdGhpcyBwYXJ0IG9mIHRoZSBjb3Vyc2UuCgpgYGB7cn0KbGlicmFyeShTZXVyYXQsIHF1aWV0bHkgPSBUKQpsaWJyYXJ5KHRpZHl2ZXJzZSwgcXVpZXRseSA9IFQpCmBgYApBbmQgdGhlbiB3ZSBjYW4gbG9hZCB1cCB0aGUgUiBvYmplY3RzIHdlIGhhdmUgdXNlZCBpbiB0aGUgcHJldmlvdXMgc2Vzc2lvbnMuCmBgYHtyfQpzZXVyYXQgPC0gcmVhZFJEUygiUm9iamVjdHMvc2V1cmF0X2ludGVncmF0ZWQuUkRTIikKYGBgCldlIGNhbiBzaW1wbHkgcnVuIHRoZSBuYW1lIG9mIHRoZSBTZXVyYXQgb2JqZWN0IChpbiBvdXIgY2FzZSBgc2V1cmF0YCkgdG8gZ2l2ZSB1cyBhIHN1bW1hcnkgb2Ygd2hhdCB0aGUgb2JqZWN0IGNvbnRhaW5zIGFuZCB3aGF0IGNhbGN1bGF0aW9ucyBoYXZlIGJlZW4gcGVyZm9ybWVkIG9uIGl0LgpgYGB7cn0Kc2V1cmF0CmBgYApXZSBjYW4gcmVjYXAgb3VyIGNsdXN0ZXJzIGJ5IHRha2luZyBhIGxvb2sgYXQgdGhlIGBEaW1QbG90YApgYGB7cn0KRGltUGxvdChzZXVyYXQsIHJlZHVjdGlvbiA9ICJ1bWFwIikKYGBgCgoKIyBGaW5kIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcwpPdXIgbmV4dCBvYmplY3RpdmUgaXMgdG8gc3RhcnQgYXNzaWduaW5nIHNvbWUgKmJpb2xvZ2ljYWwgbWVhbmluZyogdG8gb3VyIGRhdGEuIFdlIGhhdmUgc29tZSBuaWNlIGNsdXN0ZXJzIGJ1dCBubyBjbHVlIHlldCB3aGF0IGNlbGwgdHlwZXMgb3VyIGNsdXN0ZXJzIGNvbnRhaW4uCgpJbiBTZXVyYXQgd2UgY2FuIGZpbmQgdGhlIHRvcCBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMgLW9yIG1hcmtlcnMgLSB3aXRoaW4gZWFjaCBjbHVzdGVyLiBUaGUgZnVuY3Rpb24gYEZpbmRNYXJrZXJzKClgIGhhbmRsZXMgbW9zdCBvZiB0aGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gdGVzdGluZyB3aXRoaW4gdGhlIFNldXJhdCBwYWNrYWdlIGFuZCBmaW5kcyBtYXJrZXIgZ2VuZXMgd2l0aGluIGEgcGFydGljdWxhciBjbHVzdGVyLiBBcyBhIGRlZmF1bHQsIFNldXJhdCBwZXJmb3JtcyBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBiYXNlZCBvbiB0aGUgbm9uLXBhcmFtZXRlcmljIFdpbGNveG9uIHJhbmsgc3VtIHRlc3QgYnV0IG90aGVyIG1ldGhvZHMgYXJlIGF2YWlsYWJsZSB3aXRoaW4gdGhlIGZ1bmN0aW9uLiAKClRvIHN0YXJ0IHdpdGggd2UgY2FuIGZpbmQgYWxsIHRoZSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMgaW4gY2x1c3RlciAzIGNvbXBhcmVkIHRvIGV2ZXJ5IG90aGVyIGNsdXN0ZXIuIFRoZSBgbWluLnBjdGAgYXJndW1lbnQgcmVxdWlyZXMgYSBnZW5lIHRvIGJlIGRldGVjdGVkIGF0IGEgbWluaW11bSBwZXJjZW50YWdlIG9mIGFsbCB0aGUgY2VsbHMuCgpTaW5jZSB3ZSBoYXZlIHBlcmZvcm1lZCBpbnRlZ3JhdGlvbiBpbiBhIHByZXZpb3VzIHN0ZXAsIHdlIHNldCB0aGUgZGVmYXVsdCBhc3NheSBiYWNrIHRvICdSTkEnIHRvIGVuc3VyZSB3ZSBhcmUgdXNpbmcgdGhlIHJhdyBjb3VudHMsIHJhdGhlciB0aGFuIGNvdW50cyB0cmFuc2Zvcm1lZCBieSBpbnRlZ3JhdGlvbiwgZm9yIG91ciBkaWZmZXJlbnRpYWwgYW5hbHlzaXMuCmBgYHtyfQojIFdlIG1ha2Ugc3VyZSB0aGUgZGVmYXVsdCBhc3NheSBpcyBzZXQgYmFjayB0byAiUk5BIiB0byB1c2UgdGhlIG9yaWdpbmFsIGNvdW50cyBmb3IgREUgYW5hbHlzaXMuCkRlZmF1bHRBc3NheShzZXVyYXQpIDwtICJSTkEiCmBgYAoKCmBgYHtyfQpjbHVzdGVyMy5tYXJrZXJzIDwtIEZpbmRNYXJrZXJzKHNldXJhdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWRlbnQuMSA9IDMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5wY3QgID0gMC4yNSkKY2x1c3RlcjMubWFya2VycyAlPiUgaGVhZApgYGAKQSBxdWljayBsb29rIGF0IHRoZSB0b3AgNiBsaW5lcyBpbiBvdXIgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIG1hcmtlciBnZW5lIHRhYmxlIHNob3dzIHVzIHRoZSBmb3JtYXQgb2YgdGhlIGRhdGEuIFdlIGhhdmUgdGhlIHJlc3VsdHMgb2YgdGhlIFdpbGNveG9uIHJhbmsgc3VtIHRlc3QgYXMgYSBwIHZhbHVlIGFuZCBhZGp1c3RlZCBwIHZhbHVlIGZvciBtdWx0aXBsZSB0ZXN0aW5nLiBXZSBhbHNvIGhhdmUgb3VyIGF2ZXJhZ2UgbG9nMiBmb2xkIGNoYW5nZSBpbiBleHByZXNzaW9uIGJldHdlZW4gb3VyIGNsdXN0ZXJzLiBUaGUgcGN0LjEgYW5kIHBjdC4yIGNvbHVtbnMgc2hvdyB1cyB0aGUgcGVyY2VudGFnZSBvZiBjZWxscyB3aXRoaW4gdGhlIGNsdXN0ZXIgdGhlIGdlbmUgaXMgZXhwcmVzc2VkIGluICh3ZSBzZXQgYSBtaW5pbXVtIHRocmVzaG9sZCBvZiAyMCUgaW4gdGhlIGZ1bmN0aW9uKS4gQXMgZGVmYXVsdCwgdGhlIG91dHB1dCB0YWJsZSBpcyBzb3J0ZWQgYnkgYWRqdXN0ZWQgcCB2YWx1ZS4KCkEgbGl0dGxlYml0IG9mIHRhYmxlIG1hbmlwdWxhdGlvbiBpbiBgZHBseXJgIG1ha2VzIHRoZSBtYXJrZXJzIGVhc2llciB0byB2aWV3Li4uCmBgYHtyfQpjbHVzdGVyMy5tYXJrZXJzICAlPiUgCiAgc2VsZWN0KGF2Z19sb2cyRkMsIHBfdmFsLCBwX3ZhbF9hZGopICU+JQogIHNsaWNlX21heChuID0gNSwgb3JkZXJfYnkgPSBhdmdfbG9nMkZDKSAKYGBgClRvIGNoZWNrIG91ciBERSBmaW5kaW5ncywgd2UgY2FuIHZpc3VhbGl6ZSBleHByZXNzaW9uIG9mIGEgcGFydGljdWxhciBnZW5lIG9mIGludGVyZXN0IGFjcm9zcyBhbGwgY2x1c3RlcnMgdXNpbmcgdGhlIGBWbG5QbG90KClgIGZ1bmN0aW9uLiBUaGlzIHNob3dzIHRoZSBleHByZXNzaW9uIGxldmVscyBvZiBhIGdlbmUgKG9yIGdlbmVzKSBvZiBpbnRlcmVzdCB3aXRoaW4gdGhlIGNlbGxzIGFjcm9zcyBhbGwgY2x1c3RlcnMuCgpgYGB7cn0KVmxuUGxvdChzZXVyYXQsIGZlYXR1cmVzID0gIkxZWiIpCmBgYApXZSBjYW4gYWxzbyBjaGVjayB3aGljaCBjZWxscyBleHByZXNzIG91ciBnZW5lIG9mIGludGVyZXN0IG9uIHRoZSBVTUFQIGltYWdlIHVzaW5nIGBGZWF0dXJlUGxvdGAKCmBgYHtyfQpGZWF0dXJlUGxvdChzZXVyYXQsIGZlYXR1cmVzID0gIkxZWiIpCmBgYApQZXJoYXBzIHdlIGFyZSBtb3N0IGludGVyZXN0ZWQgaW4gdGhlIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyBiZXR3ZWVuIGNsdXN0ZXJzIDEgYW5kIDMKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmNsdXN0ZXIxLm1hcmtlcnMgPC0gRmluZE1hcmtlcnMoc2V1cmF0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZGVudC4xID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWRlbnQuMiA9IDMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5wY3QgPSAwLjI1LHZlcmJvc2UgPSBGQUxTRSkKY2x1c3RlcjEubWFya2VycyAlPiUgaGVhZCgpCmBgYAouLi4gb3IgdGhlIERFIGdlbmVzIGJldHdlZW4gY2x1c3RlcnMgMSBpbiBjb21wYXJpc29uIHRvIDMgYW5kIDQ/CmBgYHtyfQpjbHVzdGVyMS5tYXJrZXJzIDwtIEZpbmRNYXJrZXJzKHNldXJhdCwgaWRlbnQuMSA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkZW50LjIgPSBjKDMsNCksICMgd2UgY2FuIGdyb3VwIGNsdXN0ZXJzIHRvZ2V0aGVyIQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5wY3QgPSAwLjI1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSkKY2x1c3RlcjEubWFya2VycyAlPiUgaGVhZCgpCmBgYApVc3VhbGx5LCB3ZSBhcmUgd2FudGluZyB0byBmaW5kIHRoZSBtb3N0IGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyBpbiBlYWNoIGNsdXN0ZXIgYWNyb3NzIHRoZSB3aG9sZSBkYXRhc2V0LiBGb3IgdGhpcyB3ZSB1c2UgYEZpbmRBbGxNYXJrZXJzYC4gVGhpcyB0aW1lIHdlIHdpbGwgb25seSByZXBvcnQgdGhlIHBvc2l0aXZlbHkgZXhwcmVzc2VkIG1hcmtlcnMgd2l0aCBhIG1pbmltdW0gbG9nIGZvbGQgY2hhbmdlIG9mIDAuMjUuCgpgYGB7cn0KbWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhzZXVyYXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgIG9ubHkucG9zID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWluLnBjdCA9IDAuMjUsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGxvZ2ZjLnRocmVzaG9sZCA9IDAuMjUsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGKSAjIGtlZXBzIGl0IHF1aWV0IQptYXJrZXJzCmBgYAoKQWdhaW4sIGEgYml0IG9mIG1hbmlwdWxhdGlvbiBpbiBgZHBseXJgIGVuYWJsZXMgdXMgdG8gZ2V0IHRoZSB0b3AgNSBERSBnZW5lcyBpbiBlYWNoIGNsdXN0ZXIuCmBgYHtyfQptYXJrZXJzICU+JQogIHNlbGVjdChnZW5lLCBjbHVzdGVyLCBhdmdfbG9nMkZDLCBwX3ZhbCwgcF92YWxfYWRqKSAlPiUKICAgIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSAjIGltcG9ydGFudCB0byBsb29rIGluIGVhY2ggY2x1c3RlciEKICAgIHNsaWNlX21heChuID0gNSwgb3JkZXJfYnkgPSBhdmdfbG9nMkZDKQpgYGAKIyMgRXhlcmNpc2UKCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4KLSBVc2UgYEZpbmRNYXJrZXJzYCB0byBmaW5kIHRoZSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMgaW4gY2x1c3RlcnMgMSAmIDIgdnMgOAotIENhbiB5b3UgdXNlIGBkcGx5cmAgdG8gc2VsZWN0IGNvbHVtbnMgb2YgaW50ZXJlc3QgYW5kIG9yZGVyIHRoZW0gYnkgbG9nIGZvbGQgY2hhbmdlPwotIFVzZSBvbmUgb2YgdGhlIHZpc3VhbGlzYXRpb24gdG9vbHMgdG8gbG9vayBhdCB0aGUgZXhwcmVzc2lvbiBvZiBvbmUgb2YgdGhlIHRvcCBtYXJrZXIgZ2VuZXMKLSBCb251cyEgLSBXZSBoYXZlIG5vdCBkaXNjdXNzZWQgdGhlIHZpc3VhbGlzYXRpb24gdG9vbCBgUmlkZ2VQbG90YC4gU2VlIGlmIHlvdSBjYW4gdXNlIGl0IHRvIG1ha2UgYW5vdGhlciBwbG90IG9mIG9uZSBvZiB0aGUgdG9wIG1hcmtlciBnZW5lcy4gTW9yZSBpbmZvIGhlcmUgLSBodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L2FydGljbGVzL3Zpc3VhbGl6YXRpb25fdmlnbmV0dGUuaHRtbAo8L2Rpdj4KCmBgYHtyfQojUHV0IGV4ZXJjaXNlIHdvcmsgaW4gaGVyZSA6LSkKCgpgYGAKCgojIEFubm90YXRpbmcgbWFya2VyIGdlbmVzCgpXZSBtYXkgd2FudCB0byBzaGFyZSBvdXIgZmluZGluZ3Mgd2l0aCBjb2xsYWJvcmF0b3JzIG9yIHBlcmZvcm0gZnVydGhlciBiaW9pbmZvcm1hdGljIGFuYWx5c2lzIG9uIG91ciBERSBnZW5lcyBpbiBjbHVzdGVycyBvZiBpbnRlcmVzdC4gSW4gdGhpcyBkYXRhc2V0LCB0aGUgZ2VuZSBsZXZlbCBpbmZvcm1hdGlvbiBpcyBnaXZlbiBhcyBhIGdlbmUgbmFtZS4gV2hpbHN0IHRoaXMgaXMgdXNlZnVsIHdlIG1heSB3YW50IHRvIGVtYmVsbGlzaCB0aGVzZSByZXN1bHRzIHdpdGggc29tZSBnZW5lIGRlc2NyaXB0aW9uIGluZm9ybWF0aW9uIG9yIHNvbWUgb3RoZXIgZ2VuZSBJRHMgKGVnIEVuc2VtYmwsIEVudHJleikuCgpUaGUgYG9yZy5Icy5lZy5kYmAgZGF0YWJhc2UgaXMgYSBnZW5vbWUgd2lkZSBhbm5vdGF0aW9uIHBhY2thZ2UgZm9yIGh1bWFuIHRoYXQgaGFzIG1hbnkgZGlmZmVyZW50IGdlbmUgbWFwcGluZ3MgYmFzZWQgb24gRW50cmV6IEdlbmUgaWRlbnRpZmllcnMuIFRoZXNlIGFyZSB1cCB0byBkYXRhIGRhdGFiYXNlcyBvZiBnZW5lIGFubm90YXRpb24gdGhhdCBjYW4gYmUgaW5zdGFsbGVkIGxpa2UgYW55IG90aGVyIGJpb2NvbmR1Y3RvciBwYWNrYWdlCgpgYGB7cn0KIyBEb250IHJ1biB0aGlzIGlmIHlvdSBoYXZlIHJ1biBhbHJlYWR5IQojaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQojQmlvY01hbmFnZXI6Omluc3RhbGwoIm9yZy5Icy5lZy5kYiIpCgpsaWJyYXJ5KG9yZy5Icy5lZy5kYikKYGBgCgpJbiBvdXIgY2FzZSB3ZSBhcmUgZ29pbmcgdG8gdXNlIHRoZSBnZW5lIHN5bWJvbHMgKHJvd25hbWVzIGluIG91ciBTZXVyYXQgb2JqZWN0KSB0byBleHRyYWN0IHRoZSBnZW5lIGRlc2NyaXB0aW9uIGFuZCBlbnRyZXogSUQgZnJvbSB0aGUgZGF0YWJhc2UuCgpXZSBhcmUgZ29pbmcgdG8gZmlsdGVyIHRoZSBkYXRhYmFzZSBieSBhIGtleSBvciBzZXQgb2Yga2V5cyBpbiBvcmRlciB0byBleHRyYWN0IHRoZSBpbmZvcm1hdGlvbiB3ZSB3YW50LiBWYWxpZCBuYW1lcyBmb3IgdGhlIGtleSBjYW4gYmUgcmV0cmlldmVkIHdpdGggdGhlIGBrZXl0eXBlc2AgZnVuY3Rpb24uCgpgYGB7cn0Ka2V5dHlwZXMob3JnLkhzLmVnLmRiKQpgYGAKV2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUga2V5dHlwZSAnU1lNQk9MJyB3aGljaCBkZXNpZ25hdGVzIHRoZSBnZW5lIG5hbWVzIHRoYXQgd2UgaGF2ZSBpbiBvdXIgcm93bmFtZXMgb2YgdGhlIFNldXJhdCBvYmplY3QuCgpVbmZvcnR1bmF0ZWx5LCB0aGUgYXV0aG9ycyBvZiBgZHBseXJgIGFuZCBgQW5ub3RhdGlvbkRiaWAgaGF2ZSBib3RoIGRlY2lkZWQgdG8gdXNlIHRoZSBuYW1lIGBzZWxlY3RgIGluIHRoZWlyIHBhY2thZ2VzLiBUbyBhdm9pZCBjb25mdXNpb24gYW5kIHByb2JsZW1zIGZyb20gcGFja2FnZXMgYXR0ZW1wdGluZyB0byB1c2UgdGhlIHdyb25nIHRvb2wsIHRoZSBmb2xsb3dpbmcgY29kZSBpcyBzb21ldGltZXMgdXNlZDotCgpgQW5ub3RhdGlvbkRiaTo6c2VsZWN0YCB3aGljaCB0ZWxscyB1cyB0byB1c2UgdGhlICdzZWxlY3QnIGZ1bmN0aW9uIGZyb20gdGhlIGBBbm5vdGF0aW9uREJJYCBwYWNrYWdlLgpgYGB7cn0KYW5ubyA8LSBBbm5vdGF0aW9uRGJpOjpzZWxlY3Qob3JnLkhzLmVnLmRiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXlzPXJvd25hbWVzKHNldXJhdCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbnM9YygiU1lNQk9MIiwiR0VORU5BTUUiLCAiRU5UUkVaSUQiKSwgICMgaW5mbyB3ZSB3YW50IHRvIGV4dHJhY3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5dHlwZSA9ICJTWU1CT0wiKSAjIGluZm9ybWF0aW9uIHdlIGhhdmUKCiMgQ2hlY2sgaXQgd29ya2VkIQpoZWFkKGFubm8pICAKYGBgCk5vdyB3ZSBjYW4gY29tYmluZSBvdXIgYW5ub3RhdGlvbiB0YWJsZSB0byBvdXIgbWFya2VyIGdlbmVzIHVzaW5nIGBkcGx5cmAgYW5kIGBsZWZ0X2pvaW5gCmBgYHtyfQphbm5vX21hcmtlcnMgPC0gbWFya2VycyAlPiUgCiAgZHBseXI6OnNlbGVjdChnZW5lLCBjbHVzdGVyLCBhdmdfbG9nMkZDLCBwX3ZhbCwgcF92YWxfYWRqKSAlPiUKICBsZWZ0X2pvaW4oYW5ubywgYnkgPSBjKCJnZW5lIiA9ICJTWU1CT0wiKSkKCmFubm9fbWFya2VycyAlPiUgaGVhZCgpCgpgYGAKQXQgdGhpcyBwb2ludCB3ZSBtaWdodCB3YW50IHRvIHNhdmUgb3VyIHJlc3VsdCBpbnRvIGEgbmljZWx5IGZvcm1hdHRlZCBmaWxlIHdoZXJlIHdlIGNhbiBsb29rIHVwIG91ciBjbHVzdGVycywgdG9wIERFIGdlbmVzIGFuZCB0aGVpciBnZW5lIGRlc2NyaXB0aW9ucy4KCmBgYHtyfQphbm5vX21hcmtlcnMgJT4lIHdyaXRlX2NzdigiQW5ub3RhdGVkLnJlc3VsdHMuY3N2IikKYGBgCgojIEFzc2lnbmluZyBjbHVzdGVyIGlkZW50aXR5CgpJZiB5b3UgcmVjYWxsLCBvdXIgZGF0YXNldCBpcyBmcm9tIHRoZSBmb2xsb3dpbmcgcGFwZXI6LQoKPiBDYXJvbiBNLCBTdC1PbmdlIFAsIFNvbnRhZyBULCBXYW5nIFlDIGV0IGFsLiBTaW5nbGUtY2VsbCBhbmFseXNpcyBvZiBjaGlsZGhvb2QgbGV1a2VtaWEgcmV2ZWFscyBhIGxpbmsgYmV0d2VlbiBkZXZlbG9wbWVudGFsIHN0YXRlcyBhbmQgcmlib3NvbWFsIHByb3RlaW4gZXhwcmVzc2lvbiBhcyBhIHNvdXJjZSBvZiBpbnRyYS1pbmRpdmlkdWFsIGhldGVyb2dlbmVpdHkuIFNjaSBSZXAgMjAyMCBNYXkxNTsxMCgxKTo4MDc5CgpUaGlzIHBhcGVyIHVzZXMgdGhlIGZvbGxvd2luZyAqY2Fub25pY2FsIG1hcmtlciBnZW5lcyogdG8gYXNzaWduIGNlbGwgY2x1c3RlciBpZGVudGl0eS4gVGhpcyBzdGFydHMgdG8gZ2l2ZSBzb21lICpiaW9sb2dpY2FsIG1lYW5pbmcqIHRvIG91ciByZXN1bHRzLgoKVGhlc2UgZ2VuZXMgYXJlIGtub3duIHRvIGJlIGV4cHJlc3NlZCBpbiBhIHBhcnRpY3VsYXIgY2VsbCB0eXBlLgoKR2VuZSAgICBDZWxsIHR5cGUKLS0tLSAgICAtLS0tCkNENzlBICAgQiBDZWxscwpDRDM0ICAgIENEMzQrLCAKTVM0QTEgICBDRDIwKyBCIGNlbGxzLCAKTVpCMSAgICBCQ01BLApDU1QzICAgIE1vbm9jeXRlcywgClNQTiAgIEltbWF0dXJlIGhlbWF0b3BvaWV0aWMsIApIQkExICAgIGVyeXRocm9jeXRlcywgCkNEM0QgICAgVCBjZWxscywgCk5LRzcgICAgTksgY2VsbHMKCgpXZSBjYW4gYXNzaWduIHRoZXNlIG1hcmtlciBnZW5lcyBhcyBhIHZhcmlhYmxlCmBgYHtyfQpjZWxsX21hcmtlcnMgPC0gYygiQ0Q3OUEiLCJDRDM0IiwgIk1TNEExIiwgIk1aQjEiLCJDU1QzIiAsIlNQTiIsICJLSVQiLCAiSEJBMSIsICJDRDNEIiwgIk5LRzciKQpgYGAKCkRvdCBwbG90cyBhbGxvdyB1cyB0byBleGFtaW5lIHBhcnRpY3VsYXIgZmVhdHVyZXMgb3IgZ2VuZXMgb2YgaW50ZXJlc3QgYWNyb3NzIGFsbCBjbHVzdGVycy4gQ3JlYXRpbmcgYSBkb3RwbG90IG9mIHRoZSBjZWxsIHR5cGUgbWFya2VycyBsZXRzIHVzIGFzc2lnbiBpZGVudGl0eSBiYXNlZCBvbiB0aGVpciBleHByZXNzaW9uLgpgYGB7cn0KRG90UGxvdChzZXVyYXQsIGZlYXR1cmVzID0gY2VsbF9tYXJrZXJzKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkKYGBgCldlIGNhbiB0aGVuIG1hbnVhbGx5IGNyZWF0ZSBuZXcgaWRlbnRpdGllcyBmb3IgZWFjaCBjbHVzdGVyIGFzIGZvbGxvd3MuCgpgYGB7cn0KbmV3LmNsdXN0ZXIuaWRzIDwtIGMoIlQgQ2VsbHMiLCAjMAogICAgICAgICAgICAgICAgICAgICAiVCBDZWxscyIsICMxCiAgICAgICAgICAgICAgICAgICAgICJFcnl0aHJvY3l0ZXMiLCAjMiAgCiAgICAgICAgICAgICAgICAgICAgICJNb25vY3l0ZXMiLCAgIzMKICAgICAgICAgICAgICAgICAgICAgIkIgQ2VsbHMiLCAjNAogICAgICAgICAgICAgICAgICAgICAiQ0QzNCsiLCAjNQogICAgICAgICAgICAgICAgICAgICAiQiBDZWxscyIsICM2CiAgICAgICAgICAgICAgICAgICAgICJOSyBDZWxscyIsICM3CiAgICAgICAgICAgICAgICAgICAgICJJbW1hdHVyZSBoZW1hIzh0b3BvaWV0aWMiLCAKICAgICAgICAgICAgICAgICAgICAgIkNENzlBIEIgY2VsbHMiLCM5IAogICAgICAgICAgICAgICAgICAgICAiTW9ub2N5dGVzIiwgIzEwCiAgICAgICAgICAgICAgICAgICAgICJDRDIwKyBCIGNlbGxzIiwgIzExCiAgICAgICAgICAgICAgICAgICAgICJCQ01BIiwgIzEyCiAgICAgICAgICAgICAgICAgICAgICJFcnl0aHJvY3l0ZXMiLCAjMTMgCiAgICAgICAgICAgICAgICAgICAgICJCQ01BIikgIzE0Cm5hbWVzKG5ldy5jbHVzdGVyLmlkcykgPC0gbGV2ZWxzKHNldXJhdCkKbmV3LmNsdXN0ZXIuaWRzCnNldXJhdF9uYW1lZCA8LSBSZW5hbWVJZGVudHMoc2V1cmF0LCBuZXcuY2x1c3Rlci5pZHMpCmBgYAoKYGBge3J9CkRpbVBsb3Qoc2V1cmF0X25hbWVkLCByZWR1Y3Rpb24gPSAidW1hcCIsIGxhYmVsID0gVFJVRSwgcHQuc2l6ZSA9IDAuNSkgKyBOb0xlZ2VuZCgpCmBgYApXZSBjYW4gY2hlY2sgb3VyIHJlc3VsdCB0byB0aGUgVU1BUCBwbG90IHByb2R1Y2VkIGZyb20gdGhlIGZ1bGwgZGF0YXNldCB3aXRoaW4gdGhlIHBhcGVyIC0gbm90IHRvbyBiYWQhIENvbnNpZGVyaW5nIHdlIGhhdmUgdXNlZCBhIHN1YnNldCBhbmQgbm90IGZvbGxvd2VkIHRoZWlyIGV4YWN0IG1ldGhvZC4KIVtdKGltYWdlcy91bWFwcy5wbmcpCgoKCiMgR08gdGVybSBhbmQgcGF0aHdheSBlbnJpY2htZW50ClNvbWV0aW1lcyB3ZSB3YW50IHRvIGNhcnJ5IG91dCBhICpHZW5lIEVucmljaG1lbnQgQW5hbHlzaXMqIG9uIGEgbGlzdCBvZiBnZW5lcyBvZiBpbnRlcmVzdC4gVGhpcyBlbmFibGVzIHVzIHRvIGZpbmQgKmZ1bmN0aW9uYWwgdGVybXMqIHRoYXQgYXJlIHN0YXRpc3RpY2FsbHkgb3ZlciByZXByZXNlbnRlZCB3aXRoaW4gYSBncm91cCBvZiBnZW5lcyBjb21wYXJlZCB0byBhIGJhY2tncm91bmQgbGlzdC4gYGNsdXN0ZXJQcm9maWxlcmAgaXMgYW4gUiBwYWNrYWdlIHRoYXQgcHJvdmlkZXMgc3RhdGlzdGljYWwgdGVzdHMgZm9yIGV4cHJlc3Npb24gYW5hbHlzaXMgb2YgdGVybXMgc3VjaCBhcyBHTyAoR2VuZSBPbnRvbG9neSksIHdpdGhpbiBnZW5lIGxpc3RzIHRoYXQgaGF2ZSBzaG93biBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGRpZmZlcmVuY2VzLiAKCldoZW4gdXNpbmcgdGhpcyB0b29sIHdlIG5lZWQgdG8gaGF2ZSB0d28gaW5wdXRzOiBhIGxpc3Qgb2YgZ2VuZXMgdGhhdCBzaG93IHNpZ25pZmljYW50IGRpZmZlcmVudGlhdGlvbiBpbiBleHByZXNzaW9uIGFuZCBhIGxpc3Qgb2YgYmFja2dyb3VuZCBnZW5lcy4gSHlwZXJnZW9tZXRyaWMgdGVzdHMgYXJlIHVzZWQgZm9yIHRoZSBzdGF0aXN0aWNhbCBlbnJpY2htZW50IGFuYWx5c2lzLiAKCldlIGNhbiBjaGVjayBmb3IgR08gdGVybXMgbWVhc3VyaW5nIEJQIChiaW9sb2dpY2FsIHByb2Nlc3MpLCBDQyAoY2VsbCBjb21wb25lbnQpIGFuZCBNRiAobW9sZWN1bGFyIGZ1bmN0aW9uKS4KCkZpcnN0IG9mIGFsbCB3ZSBuZWVkIHRvIGNyZWF0ZSBvdXIgdGVzdCBnZW5lcyBhbmQgYmFja2dyb3VuZCBsaXN0CgpgYGB7cn0KbGlicmFyeShjbHVzdGVyUHJvZmlsZXIpCmJhY2tncm91bmQgPC0gYW5ubyAlPiUgcHVsbCgiRU5UUkVaSUQiKQoKZ29fbWFya2VycyA8LSBhbm5vX21hcmtlcnMgJT4lIGZpbHRlcihjbHVzdGVyID09YygwLDEpKSAlPiUgcHVsbChFTlRSRVpJRCkgIyBMZXRzIGxvb2sgYXQgY2x1c3RlcnMgKDEgKyAyIC0gIFQgY2VsbHMpCgogIGVHTyA8LSBlbnJpY2hHTygKICAgIGdlbmUgICAgICAgICAgPSBnb19tYXJrZXJzLAogICAgdW5pdmVyc2UgPSBiYWNrZ3JvdW5kLCAKICAgIE9yZ0RiICAgICAgICAgPSBvcmcuSHMuZWcuZGIsCiAgICBvbnQgICAgICAgICAgID0gIkJQIiwKICAgIHBBZGp1c3RNZXRob2QgPSAiQkgiLAogICAgcHZhbHVlQ3V0b2ZmICA9IDAuMDUsCiAgICByZWFkYWJsZSAgICAgID0gVFJVRQogICkKICAKaGVhZChlR08pCmBgYApgY2x1c3RlclByb2ZpbGVyYCBhbHNvIGhhcyBmdW5jdGlvbmFsaXR5IHRvIHBsb3QgcGF0aHdheSBkaWFncmFtcyBmb3IgdXMuIEl0IHVzZXMgb3VyIG5ldyBvYmplY3QgY3JlYXRlZCBieSBgZW5yaWNoR09gIGFzIGlucHV0IGFuZCBoaWdobGlnaHRzIHRoZSBwYXRod2F5cyBlbnJpY2hlZCBpbiBvdXIgZGF0YXNldC4KCmBgYHtyfQpwbG90R09ncmFwaChlR08pCmBgYAoKCgo=